/*
 * Copyright (c) 2011-2013, fortiss GmbH.
 * Licensed under the Apache License, Version 2.0.
 *
 * Use, modification and distribution are subject to the terms specified
 * in the accompanying license file LICENSE.txt located at the root directory
 * of this software distribution. A copy is available at
 * http://chromosome.fortiss.org/.
 *
 * This file is part of CHROMOSOME.
 *
 * $Id: udpSend.c 7664 2014-03-04 08:47:41Z geisinger $
 */

/**
 * \file
 *
 * \brief  Waypoint that copies the data on UDP network transportation.
 *
 * \author
 *         This file has been generated by the CHROMOSOME Modeling Tool (fortiss GmbH).
 */

/******************************************************************************/
/***   Includes                                                             ***/
/******************************************************************************/
#include "xme/wp/udp/include/udpSend.h"
#include "xme/com/packet.h"

#include "xme/hal/include/mem.h"

#include <inttypes.h>

/******************************************************************************/
/***   Type definitions                                                     ***/
/******************************************************************************/
/**
 * \struct udpSendConfigItem_t
 *
 * \brief  Structure for storing a configuration for this waypoint.
 */
typedef struct
{
    xme_core_dataManager_dataPacketId_t dataId; ///< Port to read data from, which will be passed on the UDP socket.
    uint16_t sizeOfData; ///< Size of the topic data (attributes not considered).
    uint16_t sizeOfTopicAndAttributes; ///< Size of topic data plus size for all attributes.
    xme_core_topic_t topic; ///< Topic associated to this port.
    void *buffer; ///< Buffer passed for writing the data to the network.
    void *key; ///< The key which is added in the packet header to correctly identify the intended receipent.
    char* hostname; ///< Hostname to which data is to be sent.
    uint16_t port; ///< Port of the given hostname to which data is to be sent.
    xme_hal_net_socketHandle_t socketHandle; ///< The opened socket descriptor where data will be pumped.
} udpSendConfigItem_t;

/******************************************************************************/
/***   Variables                                                            ***/
/******************************************************************************/
/**
 * \struct xme_wp_udp_udpSendConfigTable
 * \brief  Table for storing this waypoints configurations.
 */
static struct
{
    XME_HAL_TABLE( udpSendConfigItem_t, table, XME_WP_UDP_SEND_CONFIGURATIONTABLE_SIZE); ///< Table to store the waypoint configuration
    xme_hal_sync_criticalSectionHandle_t criticalSectionHandle; ///< Critical section handle for protecting critical regions.
} xme_wp_udp_udpSendConfigTable;

/*
 * \brief Socket handle to send udp packets. All configurations use this socket to
 *        send their data on the network using xme_hal_net_writeDatagram.
 */
static xme_hal_net_socketHandle_t udpSendSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
/*
 * \brief Socket handle to send Broadcast udp packets. All configurations use this socket to
 *        broadcast their data on the network using xme_hal_net_writeDatagram.
 */
static xme_hal_net_socketHandle_t udpSendBroadcastSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
/******************************************************************************/
/***   Prototypes                                                           ***/
/******************************************************************************/

/******************************************************************************/
/***   Implementation                                                       ***/
/******************************************************************************/
xme_status_t
xme_wp_udp_udpSend_init(void)
{
    XME_HAL_TABLE_INIT(xme_wp_udp_udpSendConfigTable.table);

    udpSendSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
    udpSendBroadcastSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
    XME_CHECK
    (
        XME_HAL_SYNC_INVALID_CRITICAL_SECTION_HANDLE != (xme_wp_udp_udpSendConfigTable.criticalSectionHandle = xme_hal_sync_createCriticalSection()),
        XME_STATUS_OUT_OF_RESOURCES
    );

    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpSend_run
(
    xme_wp_waypoint_instanceId_t instanceId
)
{
    xme_status_t status;
    xme_com_interface_address_t destinationAddress;
    char ipPort[XME_COM_INTERFACE_IPV4_STRING_BUFFER_SIZE];
    udpSendConfigItem_t *configurationItem;
    void* samplePayload;
    uint32_t bytesRead=0, bytesWritten;
    
    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);
    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpSendConfigTable.table, (xme_hal_table_rowHandle_t) instanceId);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);

    XME_CHECK
    (
        NULL != configurationItem,
        XME_STATUS_INVALID_HANDLE
    );
    //Create the packet in the given buffer space
    XME_COM_PACKET_INIT(*(xme_com_packet_sample_header_t*)(configurationItem->buffer), XME_COM_PACKET_HEADER_TYPE_SAMPLE);
    //And copy the key
    (void) xme_hal_mem_copy((void *)((xme_com_packet_sample_header_t*)(configurationItem->buffer))->key,(void *)configurationItem->key,XME_WP_UDP_HEADER_KEY_LENGTH);

    //Get the paylod from the packet
    samplePayload = XME_COM_PACKET_SAMPLE_PAYLOAD( configurationItem->buffer );

    // Indicate we are going to start the read operation.
    status = xme_core_dataHandler_startReadOperation(configurationItem->dataId);
    XME_CHECK(XME_STATUS_SUCCESS == status, XME_STATUS_INTERNAL_ERROR);

    // read from the dataId into the payload
    status = xme_core_dataHandler_readData(
            configurationItem->dataId,
            samplePayload,
            configurationItem->sizeOfData,
            &bytesRead
            );

    XME_CHECK(XME_STATUS_SUCCESS == status, XME_STATUS_INTERNAL_ERROR);
    XME_CHECK(bytesRead == configurationItem->sizeOfData, XME_STATUS_INTERNAL_ERROR);

    // Read all attributes and append them to the payload
    {
        xme_core_attribute_descriptor_list_t attributeDescriptorList;
        uint8_t i;
        uint8_t* payloadPtr = ((uint8_t*)samplePayload) + configurationItem->sizeOfData;

        status = xme_core_directory_attribute_getAttributeDescriptorList(configurationItem->topic, &attributeDescriptorList);

        if (XME_STATUS_SUCCESS == status) // When there is no attribute desciptor list for the given topic, then simply do nothing
        {
            for (i = 0; i < attributeDescriptorList.length; i++)
            {
                status = xme_core_dataHandler_readAttribute
                (
                    configurationItem->dataId,
                    attributeDescriptorList.element[i].key,
                    payloadPtr,
                    attributeDescriptorList.element[i].size,
                    &bytesRead
                );

                payloadPtr += attributeDescriptorList.element[i].size;
                
                XME_ASSERT(status == XME_STATUS_SUCCESS);

                if (status != XME_STATUS_SUCCESS || attributeDescriptorList.element[i].size != bytesRead)
                {
                    XME_LOG(XME_LOG_WARNING, "Reading of attribute '%" PRIu32 "' failed with status %" PRIu32 ".", attributeDescriptorList.element[i].key, status);
                }
            }
        }
    }

    // send the data on the network
    xme_hal_safeString_snprintf(ipPort,XME_COM_INTERFACE_IPV4_STRING_BUFFER_SIZE,"%s:%hu",configurationItem->hostname,configurationItem->port);
    xme_com_interface_ipv4StringToGenericAddress(ipPort, &destinationAddress);

    bytesWritten = xme_hal_net_writeDatagram
    (
        configurationItem->socketHandle,
        &destinationAddress,
        configurationItem->buffer,
        (configurationItem->sizeOfTopicAndAttributes + (uint16_t)sizeof(xme_com_packet_sample_header_t))
    );
    XME_CHECK_MSG
    (
        0 != bytesWritten, 
        XME_STATUS_INTERNAL_ERROR,
        XME_LOG_ALWAYS,
        "[UDPSend] xme_hal_net_writeDatagram failed with bytesWritten as 0\n"
    );

    //Complete the read operation before returning
    XME_CHECK(XME_STATUS_SUCCESS == xme_core_dataHandler_completeReadOperation(configurationItem->dataId), XME_STATUS_INTERNAL_ERROR);
    
    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpSend_getConfig
(
    xme_wp_waypoint_instanceId_t* instanceId,
    xme_core_dataManager_dataPacketId_t* dataId,
    const void *key,
    const char* hostname,
    uint16_t port
)
{
    xme_hal_table_rowHandle_t handle = XME_HAL_TABLE_INVALID_ROW_HANDLE;
    udpSendConfigItem_t* configItem=NULL;

    XME_HAL_TABLE_GET_NEXT(xme_wp_udp_udpSendConfigTable.table,
                               xme_hal_table_rowHandle_t, handle,
                               udpSendConfigItem_t, configItem,
                               (   (xme_hal_mem_compare(configItem->hostname,hostname,strlen(hostname)) == 0) 
                                   && configItem->port==port 
                                   && xme_hal_mem_compare(configItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH) == 0 ));
    if (XME_HAL_TABLE_INVALID_ROW_HANDLE != handle)
    {
        //So his already exists, we dont have to add another entry we can return the same instance ID
        *instanceId = (xme_wp_waypoint_instanceId_t) handle;
        *dataId = configItem->dataId;
        return XME_STATUS_SUCCESS;
    }
    return XME_STATUS_NOT_FOUND;
}

xme_status_t
xme_wp_udp_udpSend_addConfig
(
    xme_wp_waypoint_instanceId_t* instanceId,
    xme_core_dataManager_dataPacketId_t dataId,
    xme_core_topic_t topic,
    uint16_t sizeOfData,
    void *buffer,
    uint16_t sizeOfBuffer,
    const void* key,
    const char* hostname,
    uint16_t port,
    bool isBroadcast
)
{
    xme_hal_table_rowHandle_t configurationItemHandle;
    udpSendConfigItem_t* configurationItem;
    uint16_t sizeOfTopicAndAttributes;
    uint8_t i;
    xme_core_attribute_descriptor_list_t attributeDescriptorList;
    xme_status_t status;

    status = xme_core_directory_attribute_getAttributeDescriptorList(topic, &attributeDescriptorList);

    sizeOfTopicAndAttributes = sizeOfData;

    if (XME_STATUS_SUCCESS == status) // When there is no attribute desciptor list for the given topic, then simply do nothing
    {
        for (i = 0; i < attributeDescriptorList.length; i++)
        {
            sizeOfTopicAndAttributes += attributeDescriptorList.element[i].size;
        }
    }

    XME_CHECK
    (
        sizeOfBuffer >= (sizeOfTopicAndAttributes + (uint16_t)sizeof(xme_com_packet_sample_header_t)),
        XME_STATUS_INVALID_PARAMETER
    );

    XME_LOG(XME_LOG_DEBUG,"[udpSendWayPoint] before Broadcast Socket %d\n",udpSendBroadcastSocketHandle);
    XME_LOG(XME_LOG_DEBUG,"[udpSendWayPoint] before Generic Socket %d\n",udpSendSocketHandle);
    if (isBroadcast && XME_HAL_NET_INVALID_SOCKET_HANDLE == udpSendBroadcastSocketHandle)
    {
        //Get the broadcast socket descriptor
        udpSendBroadcastSocketHandle = xme_hal_net_createSocket
        (
            NULL,
            (XME_HAL_NET_SOCKET_BROADCAST | XME_HAL_NET_SOCKET_UDP | XME_HAL_NET_SOCKET_NONBLOCKING),
            NULL,
            0
        );
        XME_CHECK_REC
        (
            (XME_HAL_NET_INVALID_SOCKET_HANDLE != udpSendBroadcastSocketHandle),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                XME_LOG(XME_LOG_ERROR,"[udpSendWayPoint] Allocation of Broadcast Socket failed\n");
            }
        );

        status = xme_hal_net_openSocket(udpSendBroadcastSocketHandle);

        XME_CHECK_REC
        (
            (XME_STATUS_SUCCESS == status),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                XME_CHECK(XME_STATUS_SUCCESS == xme_hal_net_destroySocket(udpSendBroadcastSocketHandle), XME_STATUS_INTERNAL_ERROR);
                XME_LOG(XME_LOG_ERROR,"[udpSendWayPoint] Open of Broadcast Socket failed\n");
            
            }
        );
    }
    else if (XME_HAL_NET_INVALID_SOCKET_HANDLE == udpSendSocketHandle)
    {
        //Get the generic socket descriptor
        udpSendSocketHandle = xme_hal_net_createSocket
        (
            NULL,
            XME_HAL_NET_SOCKET_UDP | XME_HAL_NET_SOCKET_NONBLOCKING,
            NULL,
            0
        );
        XME_CHECK_REC
        (
            (XME_HAL_NET_INVALID_SOCKET_HANDLE != udpSendSocketHandle),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                XME_LOG(XME_LOG_ERROR,"[udpSendWayPoint] Allocation of Socket failed\n");
            }
        );

        status = xme_hal_net_openSocket(udpSendSocketHandle);

        XME_CHECK_REC
        (
            (XME_STATUS_SUCCESS == status),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                XME_CHECK(XME_STATUS_SUCCESS == xme_hal_net_destroySocket(udpSendSocketHandle), XME_STATUS_INTERNAL_ERROR);
                XME_LOG(XME_LOG_ERROR,"[udpSendWayPoint] Open of Socket failed\n");
            
            }
        );
    }
    XME_LOG(XME_LOG_DEBUG,"[udpSendWayPoint] After Broadcast Socket %d\n",udpSendBroadcastSocketHandle);
    XME_LOG(XME_LOG_DEBUG,"[udpSendWayPoint] After Generic Socket %d\n",udpSendSocketHandle);

    {
        xme_hal_table_rowHandle_t exists = XME_HAL_TABLE_INVALID_ROW_HANDLE;
        udpSendConfigItem_t* existsItem=NULL;
        XME_HAL_TABLE_GET_NEXT(xme_wp_udp_udpSendConfigTable.table,
                                    xme_hal_table_rowHandle_t, exists,
                                    udpSendConfigItem_t, existsItem,
                                    ((xme_hal_mem_compare(existsItem->hostname,hostname,strlen(hostname))==0)
                                     && existsItem->port==port
                                     && xme_hal_mem_compare(existsItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH)==0));
        if (XME_HAL_TABLE_INVALID_ROW_HANDLE!=exists)
        {
            //So his already exists, we dont have to add another entry we can return the same instance ID
            *instanceId = (xme_wp_waypoint_instanceId_t)exists;
            return XME_STATUS_SUCCESS;
        }
    }

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);
    configurationItemHandle = XME_HAL_TABLE_ADD_ITEM(xme_wp_udp_udpSendConfigTable.table);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);

    XME_CHECK
    (
        XME_HAL_TABLE_INVALID_ROW_HANDLE != configurationItemHandle, 
        XME_STATUS_OUT_OF_RESOURCES
    );
    *instanceId = (xme_wp_waypoint_instanceId_t)configurationItemHandle;

    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE
    (
        xme_wp_udp_udpSendConfigTable.table, 
        configurationItemHandle
    );
    
    if (NULL == configurationItem)
    {
        return XME_STATUS_INTERNAL_ERROR;
    }

    if (NULL != hostname)
    {
        configurationItem->hostname = (char *)xme_hal_mem_alloc(strlen(hostname)+1);
        XME_CHECK(NULL != xme_hal_safeString_strncpy(configurationItem->hostname, hostname, strlen(hostname)+1), XME_STATUS_INTERNAL_ERROR);
    }
    else
    {
        // This is a broadcast socket
        // Just make sure once with asserting isBroadcast
        XME_ASSERT(isBroadcast);
        // Currently we use 255.255.255.255 but actually should use one based on the subnet mask
        // TODO Issue #3155
        configurationItem->hostname = (char *)xme_hal_mem_alloc(sizeof("255.255.255.255"));
        XME_CHECK(NULL != xme_hal_safeString_strncpy(configurationItem->hostname, "255.255.255.255", sizeof("255.255.255.255") ),XME_STATUS_INTERNAL_ERROR);
    }
    configurationItem->key = (void *)xme_hal_mem_alloc(XME_WP_UDP_HEADER_KEY_LENGTH);
    xme_hal_mem_copy(configurationItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH);
    configurationItem->port = port;
    configurationItem->dataId = dataId;
    configurationItem->sizeOfData = sizeOfData;
    configurationItem->sizeOfTopicAndAttributes = sizeOfTopicAndAttributes;
    configurationItem->topic = topic;
    configurationItem->buffer = buffer;
    configurationItem->socketHandle = (isBroadcast ? udpSendBroadcastSocketHandle : udpSendSocketHandle);

    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpSend_removeConfig
(
    xme_wp_waypoint_instanceId_t instanceId
)
{
    xme_status_t status;
    udpSendConfigItem_t* configurationItem;

    XME_CHECK(XME_WP_WAYPOINT_INSTANCEID_INVALID != instanceId, XME_STATUS_INVALID_HANDLE);

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);
    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpSendConfigTable.table, (xme_hal_table_rowHandle_t)instanceId);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);

    XME_CHECK(NULL != configurationItem, XME_STATUS_INVALID_HANDLE);

    xme_hal_mem_free(configurationItem->hostname);
    xme_hal_mem_free(configurationItem->key);

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);
    status = XME_HAL_TABLE_REMOVE_ITEM(xme_wp_udp_udpSendConfigTable.table, (xme_hal_table_rowHandle_t)(instanceId));
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle);

    XME_ASSERT(XME_STATUS_SUCCESS == status || XME_STATUS_INVALID_HANDLE == status);

    return status;
}

xme_status_t
xme_wp_udp_udpSend_fini(void)
{
    xme_status_t status;
    XME_HAL_TABLE_ITERATE_BEGIN
    (
        xme_wp_udp_udpSendConfigTable.table,
        xme_hal_table_rowHandle_t, configurationItemHandle,
        udpSendConfigItem_t, configurationItem
    );
    {
        xme_hal_mem_free(configurationItem->hostname);
        xme_hal_mem_free(configurationItem->key);
    }
    XME_HAL_TABLE_ITERATE_END();

    if (XME_HAL_NET_INVALID_SOCKET_HANDLE != udpSendSocketHandle)
    {
        status = xme_hal_net_closeSocket(udpSendSocketHandle);
        XME_ASSERT(XME_STATUS_SUCCESS == status);
        status = xme_hal_net_destroySocket(udpSendSocketHandle);
        XME_ASSERT(XME_STATUS_SUCCESS == status);
        udpSendSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
    }

    if (XME_HAL_NET_INVALID_SOCKET_HANDLE != udpSendBroadcastSocketHandle)
    {
        status = xme_hal_net_closeSocket(udpSendBroadcastSocketHandle);
        XME_ASSERT(XME_STATUS_SUCCESS == status);
        status = xme_hal_net_destroySocket(udpSendBroadcastSocketHandle);
        XME_ASSERT(XME_STATUS_SUCCESS == status);
        udpSendBroadcastSocketHandle = XME_HAL_NET_INVALID_SOCKET_HANDLE;
    }

    XME_CHECK(XME_STATUS_SUCCESS == xme_hal_sync_destroyCriticalSection(xme_wp_udp_udpSendConfigTable.criticalSectionHandle), XME_STATUS_INTERNAL_ERROR);
    xme_wp_udp_udpSendConfigTable.criticalSectionHandle = XME_HAL_SYNC_INVALID_CRITICAL_SECTION_HANDLE;

    XME_HAL_TABLE_FINI(xme_wp_udp_udpSendConfigTable.table);

    return XME_STATUS_SUCCESS;
}

uint16_t
xme_wp_udp_udpSend_getPackageOverHead(void)
{
    return (uint16_t)sizeof(xme_com_packet_sample_header_t);
}
